﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Globalization;
using Fluent;
using System.Threading.Tasks;

namespace NeuroIris
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : RibbonWindow
    {

        private IrisNode[][] _irisNodes = null;

        private int NETWORK_WIDTH = 4;

        private int NETWORK_HEIGHT = 4;

        private int TIME = 4000;

        private const double SIGMA_I = 3.0;

        private const double SIGMA_F = 0.1;

        private const double EPSILON_I = 0.5;

        private const double EPSILON_F = 0.005;

        /// <summary>
        /// Numer aktualnej iteracji
        /// </summary>
        private int _currentIteration = 0;

        /// <summary>
        /// Czy rysować mapę ciepła ?
        /// </summary>
        private bool _drawHeathMap = false;

        /// <summary>
        /// Co ile cykli rysować graf
        /// </summary>
        private int _drawStep;

        private Thread _somThread = null;

        private IrisLearnPackage package;
        public MainWindow()
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
            InitializeComponent();

            package = LoadData.Load(@".\iris_data.txt");


            Random rnd = new Random(DateTime.Now.Millisecond);
        }

        private void DrawCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            double d = (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX + e.Delta * 0.001d;

            if (d <= 0.1) d = 0.1;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX = d;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleY = d;
        }

        private double[] FindWinner(IrisData iris)
        {
            IrisNode node = null;

            double sim = double.MaxValue;

            double[] cordWinner = new double[2];

            float[] dat = new float[] { iris.A, iris.B, iris.C, iris.D };

            for (int y = 0; y < _irisNodes.Length; y++)
            {
                for (int x = 0; x < _irisNodes[0].Length; x++)
                {
                    float s = _irisNodes[y][x].Similarity(dat);

                    if (sim > s)
                    {
                        sim = s;
                        node = _irisNodes[y][x];
                        cordWinner[0] = y;
                        cordWinner[1] = x;
                    }

                }
            }

            return cordWinner;
        }

        private void SOM()
        {
            double sigma, h, epsilon, distance;
            for (_currentIteration = 0; _currentIteration < TIME; _currentIteration++)
            {
                //mamy zwycięzce, teraz musimy zmienic wagi W wektorów - zwycięscy i jego sąsiadów                
                epsilon = Epsilon(_currentIteration);
                sigma = Gaussian(_currentIteration);

                foreach (var IrisData in package.Data)
                {
                    // szukamy zwycięzcy
                    double[] cordWinner = FindWinner(IrisData);

                    Parallel.For(0, _irisNodes.Length, (y) =>
                    {
                        for (int x = 0; x < _irisNodes[0].Length; x++)
                        {
                            distance = ManhattanDistance(cordWinner[0], cordWinner[1], y, x);
                            h = AdaptationStrength(sigma, distance);
                            _irisNodes[y][x].W[0] += (float)epsilon * (float)h * (IrisData.A - _irisNodes[y][x].W[0]);
                            _irisNodes[y][x].W[1] += (float)epsilon * (float)h * (IrisData.B - _irisNodes[y][x].W[1]);
                            _irisNodes[y][x].W[2] += (float)epsilon * (float)h * (IrisData.C - _irisNodes[y][x].W[2]);
                            _irisNodes[y][x].W[3] += (float)epsilon * (float)h * (IrisData.D - _irisNodes[y][x].W[3]);

                            _irisNodes[y][x].Hit += (float)h;

                            if (minH > _irisNodes[y][x].Hit) minH = _irisNodes[y][x].Hit;
                            if (maxH < _irisNodes[y][x].Hit) maxH = _irisNodes[y][x].Hit;

                            IrisNode node = _irisNodes[y][x];

                            switch (IrisData.Iris)
                            {
                                case IrisEnum.IrisVirginica:
                                    node.VirginicaCount += (int)(10 * h);
                                    break;

                                case IrisEnum.IrisVersicolor:
                                    node.VersicolorCount += (int)(10 * h);
                                    break;

                                case IrisEnum.IrisSetosa:
                                    node.SetosaCount += (int)(10 * h);
                                    break;
                            }
                        }
                    });
                }

                if (_currentIteration % _drawStep == 0)
                {
                    Dispatcher.Invoke(new DispatcherDelegate(Colorize));
                }
            }
        }

        //przy uzyciu L-normy (odleglosc Manhattan) mierzymy jak bardzo jednostka r (rNode) jest przystosowana, kiedy s (winnerNode) jest zwyciesca  
        private double ManhattanDistance(double winnerI, double winnerJ, double k, double m)
        {
            double d = 0;

            d = Math.Abs(winnerI - k) + Math.Abs(winnerJ - m);

            return d;
        }

        // odchylenie standardowe sigma
        private double Gaussian(int t)
        {
            double sigma = 0;

            sigma = SIGMA_I * Math.Pow(SIGMA_F / SIGMA_I, (double)t / TIME);

            return sigma;
        }

        private double AdaptationStrength(double sigma, double d)
        {
            double h = 0;

            h = Math.Exp(-(d * d) / (2 * (sigma * sigma)));

            return h;
        }

        private double Epsilon(int t)
        {
            double epsilon = 0;

            epsilon = EPSILON_I * Math.Pow(EPSILON_F / EPSILON_I, (double)t / TIME);

            return epsilon;
        }

        private float minH = 0;
        private float maxH = 0;

        bool _setosaOnly = false;
        bool _virgiOnly = false;
        bool _versiOnly = false;

        private void CreateNetwork()
        {
            DrawCanvas.Children.Clear();
            minH = float.MaxValue;
            maxH = float.MinValue;

            NETWORK_HEIGHT = (int)spinnerY.Value;
            NETWORK_WIDTH = (int)spinnerX.Value;
            TIME = (int)iter.Value;
            _drawStep = (int)drawStep.Value;

            

            _irisNodes = new IrisNode[NETWORK_HEIGHT][];


            for (int y = 0; y < NETWORK_HEIGHT; y++)
            {
                Line line = new Line();
                line.StrokeThickness = 2;
                line.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
                line.X1 = 7;
                line.Y1 = 7 + 28 * y;

                line.X2 = 7 + 28 * (NETWORK_WIDTH - 1);
                line.Y2 = line.Y1;
                DrawCanvas.Children.Add(line);
            }

            for (int x = 0; x < NETWORK_WIDTH; x++)
            {
                Line line = new Line();
                line.StrokeThickness = 2;
                line.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
                line.X1 = 7 + 28 * x;
                line.Y1 = 7;

                line.X2 = line.X1;
                line.Y2 = 7 + 28 * (NETWORK_HEIGHT - 1);
                DrawCanvas.Children.Add(line);
            }

            for (int y = 0; y < NETWORK_HEIGHT; y++)
            {
                _irisNodes[y] = new IrisNode[NETWORK_WIDTH];
                for (int x = 0; x < NETWORK_WIDTH; x++)
                {

                    IrisNode iris = new IrisNode();
                    iris.Width = 15;
                    iris.Height = 15;
                    iris.W = package.RandomW();
                    iris.Hit = 0f;
                    iris.SetosaCount = 0;
                    iris.VersicolorCount = 0;
                    iris.VirginicaCount = 0;

                    Canvas.SetLeft(iris, x * 28);
                    Canvas.SetTop(iris, y * 28);

                    _irisNodes[y][x] = iris;
                    DrawCanvas.Children.Add(iris);
                }
            }
        }

        private void Run()
        {
            try
            {
                Dispatcher.Invoke(new DispatcherDelegate(BeginCalculation));
                SOM();
            }
            catch (Exception ex)
            {

            }
            finally
            {
                Dispatcher.Invoke(new DispatcherDelegate(EndCalculation));
            }
        }

        private delegate void DispatcherDelegate();

        private void BeginCalculation()
        {

            btnStart.IsEnabled = false;
            btnStop.IsEnabled = true;
            iter.IsEnabled = false;
            drawStep.IsEnabled = false;
            heatMap.IsEnabled = false;
            statusBar.Maximum = TIME;
            statusBar.Value = 0;
            statusBar.IsEnabled = true;
            spinnerX.IsEnabled = false;
            spinnerY.IsEnabled = false;
            ColorGroup.IsEnabled = false;
            ZoomGroup.IsEnabled = true;
        }

        private void EndCalculation()
        {

            btnStart.IsEnabled = true;
            btnStop.IsEnabled = false;
            iter.IsEnabled = true;
            drawStep.IsEnabled = true;
            heatMap.IsEnabled = true;
            statusBar.Value = 1;
            statusBar.IsEnabled = false;
            spinnerX.IsEnabled = true;
            spinnerY.IsEnabled = true;
            ColorGroup.IsEnabled = true;
            ZoomGroup.IsEnabled = true;

            Colorize();
        }

        private void Colorize()
        {
            statusBar.Value = _currentIteration;
            if (!heatMap.IsChecked)
            {
                _setosaOnly = SetOsaOnly.IsChecked;
                _virgiOnly = VirgiOnly.IsChecked;
                _versiOnly = VersiOnly.IsChecked;

                for (int y = 0; y < _irisNodes.Length; y++)
                {
                    for (int x = 0; x < _irisNodes[0].Length; x++)
                    {
                        int max = 0;

                        if (max < _irisNodes[y][x].VersicolorCount) max = _irisNodes[y][x].VersicolorCount;
                        if (max < _irisNodes[y][x].SetosaCount) max = _irisNodes[y][x].SetosaCount;
                        if (max < _irisNodes[y][x].VirginicaCount) max = _irisNodes[y][x].VirginicaCount;

                        byte r = _versiOnly ? (byte)(_irisNodes[y][x].VersicolorCount / (float)max * 255) : (byte)0;
                        byte g = _setosaOnly ? (byte)(_irisNodes[y][x].SetosaCount / (float)max * 255) : (byte)0;
                        byte b = _virgiOnly ? (byte)(_irisNodes[y][x].VirginicaCount / (float)max * 255) : (byte)0;

                        Brush br = new SolidColorBrush(Color.FromRgb(r,g,b));
                        _irisNodes[y][x].SetColor(br);
                    }
                }
            }
            else
            {

                float d = (maxH - minH);

                for (int y = 0;y < _irisNodes.Length; y++)
                {
                    for (int x = 0; x < _irisNodes[0].Length; x++)
                    {
                        byte k = (byte)((_irisNodes[y][x].Hit - minH) / d * 255);

                        Brush b = new SolidColorBrush(Color.FromRgb(k, k, k));
                        _irisNodes[y][x].SetColor(b);
                    }
                }
            }
        }


        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if ((_somThread == null) || (!_somThread.IsAlive))
            {
                CreateNetwork();
                _somThread = new Thread(new ThreadStart(Run));
                _somThread.Start();
            }
            else
            {
                MessageBox.Show("Trwają obliczenia");
            }
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            if ((_somThread != null) && (_somThread.IsAlive))
            {
                _somThread.Abort();
                _somThread = null;
                EndCalculation();
            }
        }


        private void heatMap_Checked(object sender, EventArgs e)
        {
            Colorize();
        }

        private void heatMap_Unchecked(object sender, EventArgs e)
        {
            Colorize();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void RibbonWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if ((_somThread != null) && (_somThread.IsAlive))
            {
                _somThread.Abort();
            }
        }

        private void ZoomIn_Click(object sender, RoutedEventArgs e)
        {
            double d = (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX + 0.1d;

            if (d <= 0.1) d = 0.1;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX = d;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleY = d;
        }

        private void ZoomOut_Click(object sender, RoutedEventArgs e)
        {
            double d = (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX - 0.1d;

            if (d <= 0.1) d = 0.1;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleX = d;
            (DrawCanvas.LayoutTransform as ScaleTransform).ScaleY = d;
        }


        private void SetOsaOnly_Checked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

        private void SetOsaOnly_Unchecked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

        private void VersiOnly_Checked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }


        private void VirgiOnly_Checked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

        private void VirgiOnly_Unchecked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

        private void VersiOnly_Unchecked(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

        private void VirgiOnly_Unchecked_1(object sender, EventArgs e)
        {
            if (_irisNodes != null)
            {
                Colorize();
            }
        }

    }
}
